use std::{io::{Read, Write, self}, path::Path, fs::File};

use sha2::{Sha256, Digest};

pub struct StreamWrapper<T: Read + Write>(pub T);

pub struct StreamError();

impl<T: Read + Write> StreamWrapper<T> {
    pub fn send(&mut self, buf: &[u8]) -> Result<(), StreamError> {
        self.0.write_all(buf)
            .map_err(|_| StreamError())
    }

    pub fn recv(&mut self, buf: &mut [u8]) -> Result<(), StreamError> {
        self.0.read_exact(buf)
            .map_err(|_| StreamError())
    }

    pub fn send_u8(&mut self, data: u8) -> Result<(), StreamError> {
        self.send(&[data])
    }

    pub fn recv_u8(&mut self) -> Result<u8, StreamError> {
        let mut buf = [0; 1];
        self.recv(& mut buf)?;
        Ok(buf[0])
    }

    pub fn send_u64(&mut self, data: u64) -> Result<(), StreamError> {
        self.send(&data.to_le_bytes())
    }

    pub fn recv_u64(&mut self) -> Result<u64, StreamError> {
        let mut buf = [0; 8];
        self.recv(&mut buf)?;
        Ok(u64::from_le_bytes(buf))
    }

    pub fn send_buf8(&mut self, buf: &[u8]) -> Result<(), StreamError> {
        let len = if buf.len() > 255 { 255 } else { buf.len() };
        self.send_u8(len.try_into().unwrap())?;
        self.send(&buf[..len])
    }

    pub fn recv_buf8(&mut self) -> Result<Vec<u8>, StreamError> {
        let len = self.recv_u8()?;
        let mut buf = vec![0; len.into()];
        self.recv(&mut buf)?;
        Ok(buf)
    }

    pub fn recv_str8(&mut self) -> Result<String, StreamError> {
        String::from_utf8(self.recv_buf8()?)
            .map_err(|_| StreamError())
    }
}

pub fn create_password_hash(data: &[u8]) -> Result<Vec<u8>, getrandom::Error> {
    let mut salt = [0; 32];
    getrandom::getrandom(&mut salt)?;

    let mut hasher = Sha256::new();
    hasher.update(salt);
    hasher.update(data);
    hasher.update(salt);
    let hash = hasher.finalize().into();

    Ok([salt, hash].concat())
}

pub fn check_password_hash(data: &[u8], hash: &[u8]) -> bool {
    let salt = &hash[..32];

    let mut hasher = Sha256::new();
    hasher.update(salt);
    hasher.update(data);
    hasher.update(salt);
    let real_hash: [u8; 32] = hasher.finalize().into();

    hash[32..] == real_hash
}

pub fn stream_hash(stream: &mut impl Read) -> Result<Vec<u8>, std::io::Error> {
    let mut hasher = Sha256::new();

    io::copy(stream, &mut hasher)?;

    Ok(hasher.finalize().to_vec())
}

pub fn file_hash(path: impl AsRef<Path>) -> Result<Vec<u8>, std::io::Error> {
    stream_hash(&mut File::open(path)?)
}


pub fn get_file_name(path: &str) -> Option<String> {
    if let Some(name) = Path::new(path).file_name() {
        if let Some(name) = name.to_str() {
            return Some(name.to_owned());
        }
    }
    None
}

pub fn is_valid_file_name(name: &str) -> bool {
    if let Some(f_name) = get_file_name(name) {
        if f_name == name {
            return true;
        }
    }
    false
}
